package com.gitee.wsl.func.filter.bloomfilter.api

import com.gitee.wsl.func.filter.bloomfilter.bean.Shape
import com.gitee.wsl.func.predicate.Predicate
import com.gitee.wsl.func.predicate.BiPredicate
import kotlin.math.round


/**
 * The interface that describes a Bloom filter.
 *
 *
 * *See implementation notes for [BitMapExtractor] and [IndexExtractor].*
 *
 *
 * @param <T> The BloomFilter type.
 * @see BitMapExtractor
 *
 * @see IndexExtractor
 *
 * @since 4.5.0-M1
</T> */
interface BloomFilter<T : BloomFilter<T>> : IndexExtractor, BitMapExtractor {
    /**
     * Gets the cardinality (number of enabled bits) of this Bloom filter.
     *
     *
     * This is also known as the Hamming value or Hamming number.
     *
     * @return the cardinality of this filter
     */
    fun cardinality(): Int

    // Query Operations
    /**
     * Gets the characteristics of the filter.
     *
     *
     * Characteristics are defined as bits within the characteristics integer.
     *
     *
     * @return the characteristics for this bloom filter.
     */
    fun characteristics(): Int

    /**
     * Clears the filter to by resetting it to its initial, unpopulated state.
     */
    fun clear()

    /**
     * Returns `true` if this filter contains the bits specified in the bit maps produced by the
     * bitMapExtractor.
     *
     * @param bitMapExtractor the `BitMapExtractor` to provide the bit maps.
     * @return `true` if this filter is enabled for all bits specified by the bit maps
     */
    fun contains(bitMapExtractor: BitMapExtractor): Boolean {
        return processBitMapPairs(bitMapExtractor,BiPredicate { x, y -> (x and y) == y })
    }

    /**
     * Returns `true` if this filter contains the specified filter.
     *
     *
     * Specifically this
     * returns `true` if this filter is enabled for all bits that are enabled in the
     * `other` filter. Using the bit representations this is
     * effectively `(this AND other) == other`.
     *
     * @param other the other Bloom filter
     * @return true if all enabled bits in the other filter are enabled in this filter.
     */
    fun contains(other: BloomFilter<*>): Boolean {
        return if ((characteristics() and SPARSE) != 0) contains(other as IndexExtractor) else contains((other as BitMapExtractor))
    }

    /**
     * Returns `true` if this filter contains the bits specified in the hasher.
     *
     *
     * Specifically this returns `true` if this filter is enabled for all bit indexes
     * identified by the `hasher`. Using the bit map representations this is
     * effectively `(this AND hasher) == hasher`.
     *
     * @param hasher the hasher to provide the indexes
     * @return true if this filter is enabled for all bits specified by the hasher
     */
    fun contains(hasher: Hasher): Boolean {
        val shape = this.shape
        return contains(hasher.indices(shape))
    }

    /**
     * Returns `true` if this filter contains the indices specified IndexExtractor.
     *
     *
     * Specifically this returns `true` if this filter is enabled for all bit indexes
     * identified by the `IndexExtractor`.
     *
     * @param indexExtractor the IndexExtractor to provide the indexes
     * @return `true` if this filter is enabled for all bits specified by the IndexExtractor
     */
    fun contains(indexExtractor: IndexExtractor): Boolean

    /**
     * Creates a new instance of this [BloomFilter] with the same properties as the current one.
     *
     * @return a copy of this [BloomFilter].
     */
    fun copy(): T

    // update operations
    /**
     * Estimates the number of items in the intersection of this Bloom filter with the other bloom filter.
     *
     *
     * This method produces estimate is roughly equivalent to the number of unique Hashers that have been merged into both
     * of the filters by rounding the value from the calculation described in the [Shape] class Javadoc.
     *
     *
     * *`estimateIntersection` should only be called with Bloom filters of the same Shape.  If called on Bloom
     * filters of differing shape this method is not symmetric. If `other` has more bits an `IllegalArgumentException`
     * may be thrown.*
     *
     * @param other The other Bloom filter
     * @return an estimate of the number of items in the intersection. If the calculated estimate is larger than Integer.MAX_VALUE then MAX_VALUE is returned.
     * @throws IllegalArgumentException if the estimated N for the union of the filters is infinite.
     * @see .estimateN
     * @see Shape
     */
    fun estimateIntersection(other: BloomFilter<*>): Int {
        val eThis = this.shape.estimateN(cardinality())
        val eOther = this.shape.estimateN(other.cardinality())
        if (eThis.isInfinite() && eOther.isInfinite()) {
            // if both are infinite the union is infinite and we return Integer.MAX_VALUE
            return Int.Companion.MAX_VALUE
        }
        var estimate: Long
        // if one is infinite the intersection is the other.
        if (eThis.isInfinite()) {
            estimate = round(eOther).toLong()
        } else if (eOther.isInfinite()) {
            estimate = round(eThis).toLong()
        } else {
            val union = this.copy()
            union.merge(other)
            val eUnion = this.shape.estimateN(union.cardinality())
            require(!eUnion.isInfinite()) { "The estimated N for the union of the filters is infinite" }
            // maximum estimate value using integer values is: 46144189292 thus
            // eThis + eOther cannot overflow the long value.
            estimate = round(eThis + eOther - eUnion).toLong()
            estimate = if (estimate < 0) 0 else estimate
        }
        return if (estimate > Int.Companion.MAX_VALUE) Int.Companion.MAX_VALUE else estimate.toInt()
    }

    /**
     * Estimates the number of items in the Bloom filter.
     *
     *
     * By default this is the rounding of the `Shape.estimateN(cardinality)` calculation for the
     * shape and cardinality of this filter.
     *
     *
     * This produces an estimate roughly equivalent to the number of Hashers that have been merged into the filter
     * by rounding the value from the calculation described in the [Shape] class Javadoc.
     *
     *
     * *Note:*
     *
     *  * if cardinality == numberOfBits, then result is Integer.MAX_VALUE.
     *  * if cardinality &gt; numberOfBits, then an IllegalArgumentException is thrown.
     *
     *
     * @return an estimate of the number of items in the bloom filter.  Will return Integer.MAX_VALUE if the
     * estimate is larger than Integer.MAX_VALUE.
     * @throws IllegalArgumentException if the cardinality is &gt; numberOfBits as defined in Shape.
     * @see Shape.estimateN
     * @see Shape
     */
    fun estimateN(): Int {
        val d = this.shape.estimateN(cardinality())
        if (d.isInfinite()) {
            return Int.Companion.MAX_VALUE
        }
        require(!d.isNaN()) { "Cardinality too large: " + cardinality() }
        val l: Long = round(d).toLong()
        return if (l > Int.Companion.MAX_VALUE) Int.Companion.MAX_VALUE else l.toInt()
    }

    /**
     * Estimates the number of items in the union of this Bloom filter with the other bloom filter.
     *
     *
     * This produces an estimate roughly equivalent to the number of unique Hashers that have been merged into either
     * of the filters by rounding the value from the calculation described in the [Shape] class Javadoc.
     *
     *
     * *`estimateUnion` should only be called with Bloom filters of the same Shape.  If called on Bloom
     * filters of differing shape this method is not symmetric. If `other` has more bits an `IllegalArgumentException`
     * may be thrown.*
     *
     * @param other The other Bloom filter
     * @return an estimate of the number of items in the union.  Will return Integer.MAX_VALUE if the
     * estimate is larger than Integer.MAX_VALUE.
     * @see .estimateN
     * @see Shape
     */
    fun estimateUnion(other: BloomFilter<*>): Int {
        val copy = this.copy()
        copy.merge(other)
        return copy.estimateN()
    }

    /**
     * Gets the shape that was used when the filter was built.
     * @return The shape the filter was built with.
     */
    val shape: Shape

    // Counting Operations
    /**
     * Determines if all the bits are off. This is equivalent to
     * `cardinality() == 0`.
     *
     *
     *
     * *Note: This method is optimised for non-sparse filters.* Implementers
     * are encouraged to implement faster checks if possible.
     *
     *
     * @return `true` if no bits are enabled, `false` otherwise.
     */
    fun isEmpty(): Boolean= processBitMaps(Predicate { y: Long -> y == 0L })

    /**
     * Determines if the bloom filter is "full".
     *
     *
     * Full is defined as having no unset bits.
     *
     * @return `true` if the filter is full, `false` otherwise.
     */
    fun isFull(): Boolean = cardinality() == this.shape.numberOfBits

    /**
     * Merges the specified hasher into this Bloom filter. Specifically all
     * bit indexes that are identified by the `bitMapExtractor` will be enabled in this filter.
     *
     *
     * *Note: This method should return `true` even if no additional bit indexes were
     * enabled. A `false` result indicates that this filter may or may not contain all the indexes
     * enabled in the `bitMapExtractor`.*  This state may occur in complex Bloom filter implementations like
     * counting Bloom filters.
     *
     * @param bitMapExtractor The BitMapExtractor to merge.
     * @return true if the merge was successful
     * @throws IllegalArgumentException if bitMapExtractor sends illegal value.
     */
    fun merge(bitMapExtractor: BitMapExtractor): Boolean

    /**
     * Merges the specified Bloom filter into this Bloom filter.
     *
     *
     * Specifically all
     * bit indexes that are identified by the `other` will be enabled in this filter.
     *
     *
     * *Note: This method should return `true` even if no additional bit indexes were
     * enabled. A `false` result indicates that this filter may or may not contain
     * the `other` Bloom filter.*  This state may occur in complex Bloom filter implementations like
     * counting Bloom filters.
     *
     * @param other The bloom filter to merge into this one.
     * @return true if the merge was successful
     */
    fun merge(other: BloomFilter<*>): Boolean {
        return if ((characteristics() and SPARSE) != 0) merge(other as IndexExtractor) else merge(other as BitMapExtractor)
    }

    /**
     * Merges the specified hasher into this Bloom filter. Specifically all
     * bit indexes that are identified by the `hasher` will be enabled in this filter.
     *
     *
     * *Note: This method should return `true` even if no additional bit indexes were
     * enabled. A `false` result indicates that this filter may or may not contain
     * the `hasher` values.*  This state may occur in complex Bloom filter implementations like
     * counting Bloom filters.
     *
     * @param hasher The hasher to merge.
     * @return true if the merge was successful
     * @throws IllegalArgumentException if hasher produces an illegal value.
     */
    fun merge(hasher: Hasher): Boolean {
        return merge(hasher.indices(this.shape))
    }

    /**
     * Merges the specified IndexExtractor into this Bloom filter. Specifically all
     * bit indexes that are identified by the `indexExtractor` will be enabled in this filter.
     *
     *
     * *Note: This method should return `true` even if no additional bit indexes were
     * enabled. A `false` result indicates that this filter may or may not contain all the indexes of
     * the `indexExtractor`.*  This state may occur in complex Bloom filter implementations like
     * counting Bloom filters.
     *
     * @param indexExtractor The IndexExtractor to merge.
     * @return true if the merge was successful
     * @throws IllegalArgumentException if indexExtractor sends illegal value.
     */
    fun merge(indexExtractor: IndexExtractor): Boolean

    /**
     * Most Bloom filters create unique IndexExtractors.
     */
    override fun uniqueIndices(): IndexExtractor {
        return this
    }

    companion object {
        /**
         * The sparse characteristic used to determine the best method for matching: {@value}.
         *
         *
         * For `sparse` implementations the `forEachIndex(IntConsumer consumer)` method is more efficient. For non `sparse` implementations the
         * `forEachBitMap(LongConsumer consumer)` is more efficient. Implementers should determine if it is easier.
         *
         */
        const val SPARSE: Int = 0x1
    }
}
